/*
 * Copyright 2011 The Rusted Roof, LLC.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package org.proteusframework.platformservice;

import org.proteusframework.core.api.IPlatformDelegate;
import org.proteusframework.core.api.IRuntimeEngine;
import org.proteusframework.core.api.config.IConfiguration;
import org.proteusframework.core.api.model.INamespace;
import org.proteusframework.core.runtime.StandardProteusApplication;
import org.proteusframework.core.util.Assert;
import org.proteusframework.platformservice.appconfig.IAppConfigService;
import org.proteusframework.platformservice.lifecycle.api.ILifecycleService;
import org.proteusframework.platformservice.messaging.IMessageService;
import org.proteusframework.platformservice.persistence.api.IPersistenceService;
import org.proteusframework.platformservice.theme.IThemeService;

import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Provides an extended Proteus Framework application that intrinsically includes access to various Proteus Platform
 * Services. This application context suitable for nearly every imaginable application built atop the Proteus Framework.
 * <p/>
 * To access <b></b>Proteus Platform Services</b>, an application must include the platform services library in the
 * <code>CLASSPATH</code>, and use code like this when bootstrapping the application:
 * <p/>
 * <code>
 * IProteusApplication myApplication = new ExtendedProteusApplication();
 * IRuntimeEngine myRuntimeEngine = new YourCustomExtendedApplication();
 * myApplication.setRuntimeEngine(myRuntimeEngine);
 * myApplication.startApplication();
 * </code>
 *
 * @author Tacoma Four
 */
public final class ExtendedProteusApplication extends StandardProteusApplication implements IExtendedPlatformDelegate
{
    private static final Logger logger = Logger.getLogger(ExtendedProteusApplication.class.getCanonicalName());

    private IExtendedRuntimeConfiguration extCfg;

    private boolean isProteusPlatformServicesInstalled = false;

    private final ServiceScopeManager serviceScopeManager = new ServiceScopeManager();

    public ExtendedProteusApplication()
    {
        serviceScopeManager.setPlatformDelegate(this, IExtendedPlatformDelegate.class);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setRuntimeEngine(IRuntimeEngine engine)
    {
        Assert.parameterNotNull(engine, "Parameter 'engine' must not be null");

        isProteusPlatformServicesInstalled = IExtendedRuntimeEngine.class.isAssignableFrom(engine.getClass());

        this.engine = engine;

        if (logger.isLoggable(Level.FINEST))
        {
            logger.finest("Injecting platform delegate into runtime engine");
        }

        if (isProteusPlatformServicesInstalled)
        {
            IExtendedRuntimeEngine extEngine = getEngine(IExtendedRuntimeEngine.class);
            this.extCfg = extEngine.getExtendedRuntimeConfiguration();
            getEngine().setPlatformDelegate(this, IExtendedPlatformDelegate.class);

            logger.config("An Extended Proteus Delegate was configured");
            logger.config("Proteus Platform Services are available to the application");
        } else
        {
            getEngine().setPlatformDelegate(this, IPlatformDelegate.class);
            logger.config("A Standard Proteus Delegate was configured");
        }
    }

    /**
     * Request the active instance of the specified platform service, if the service is installed in the given
     * runtime environment.
     *
     * @param serviceClassification Platform service to locate
     * @param expectedInterface     Expected interface of the given platform service
     * @return Active instance of the specified platform service
     * @throws ServiceNotInstalledException When the specified platform service is not installed within the current
     *                                      runtime engine
     */
    @Override
    public <T extends IServicePlugin> T getPlatformService(ServiceClassification serviceClassification,
                                                           Class<T> expectedInterface)
            throws ServiceNotInstalledException
    {
        return extCfg.getPlatformService(serviceClassification, expectedInterface);
    }

    @Override
    public <T extends IServicePlugin> T getPlatformService(INamespace namespace, Class<T> expectedInterface) throws ServiceNotInstalledException
    {
        // TODO: Implement secondary platform services lookup
        throw new UnsupportedOperationException("Secondary platform services are not yet supported");
    }

    @Override
    public <T extends IServicePlugin> IConfiguration getServiceConfiguration(Class<T> serviceInterface)
    {
        T implementation = getPlatformService(serviceInterface);
        return (IConfiguration) implementation;
    }

    @Override
    public <T extends IServicePlugin> IConfiguration getServiceConfiguration(INamespace namespace, Class<T> serviceInterface) throws ServiceNotInstalledException
    {
        T implementation = getPlatformService(namespace, serviceInterface);
        return (IConfiguration) implementation;
    }

    @Override
    public <T extends IServicePlugin> T getPlatformService(Class<T> expectedInterface, ServiceScope scope) throws ServiceScopingException
    {
        return serviceScopeManager.getPlatformService(expectedInterface, scope);
    }

    @Override
    public <T extends IServicePlugin> T getPlatformService(Class<T> expectedInterface, ServiceScope scope, INamespace namespace) throws ServiceScopingException
    {
        return serviceScopeManager.getPlatformService(expectedInterface, scope, namespace);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <T extends IServicePlugin> T getPlatformService(Class<T> expectedInterface) throws ServiceNotInstalledException
    {
        if (IAppConfigService.class.isAssignableFrom(expectedInterface))
        {
            return getPlatformService(ServiceClassification.ApplicationConfiguration, expectedInterface);
        } else if (ILifecycleService.class.isAssignableFrom(expectedInterface))
        {
            return getPlatformService(ServiceClassification.Lifecycle, expectedInterface);
        } else if (IPersistenceService.class.isAssignableFrom(expectedInterface))
        {
            return getPlatformService(ServiceClassification.Persistence, expectedInterface);
        } else if (IThemeService.class.isAssignableFrom(expectedInterface))
        {
            return getPlatformService(ServiceClassification.Theme, expectedInterface);
        } else
        {
            return getPlatformService(ServiceClassification.Custom, expectedInterface);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IAppConfigService getAppConfigService()
    {
        Assert.assertTrue(isProteusPlatformServicesInstalled, "Proteus Platform Services are not available");

        return extCfg.getPlatformService(ServiceClassification.ApplicationConfiguration, IAppConfigService.class);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ILifecycleService getLifecycleService()
    {
        Assert.assertTrue(isProteusPlatformServicesInstalled, "Proteus Platform Services are not available");

        return extCfg.getPlatformService(ServiceClassification.Lifecycle, ILifecycleService.class);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isThemeServiceAvailable()
    {
        boolean flag = false;

        try
        {
            extCfg.getPlatformService(ServiceClassification.Theme, IThemeService.class);
            flag = true;
        } catch (ServiceNotInstalledException e)
        {
            flag = false;
        }

        return flag;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IThemeService getThemeService()
    {
        Assert.assertTrue(isProteusPlatformServicesInstalled, "Proteus Platform Services are not available");

        return extCfg.getPlatformService(ServiceClassification.Theme, IThemeService.class);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IPersistenceService getPersistenceService()
    {
        Assert.assertTrue(isProteusPlatformServicesInstalled, "Proteus Platform Services are not available");

        return extCfg.getPlatformService(ServiceClassification.Persistence, IPersistenceService.class);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isPersistenceServiceAvailable()
    {
        boolean flag = false;

        try
        {
            extCfg.getPlatformService(ServiceClassification.Persistence, IPersistenceService.class);
            flag = true;
        } catch (ServiceNotInstalledException e)
        {
            flag = false;
        }

        return flag;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isMessageServiceAvailable()
    {
        boolean flag = false;

        try
        {
            extCfg.getPlatformService(ServiceClassification.Messaging, IMessageService.class);
            flag = true;
        } catch (ServiceNotInstalledException e)
        {
            flag = false;
        }

        return flag;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IMessageService getMessageService()
    {
        Assert.assertTrue(isProteusPlatformServicesInstalled, "Proteus Platform Services are not available");

        return extCfg.getPlatformService(ServiceClassification.Messaging, IMessageService.class);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <T extends IPlatformDelegate> T adapt(Class<T> delegateClassType)
    {
        T targetDelegate = null;

        if (delegateClassType.isAssignableFrom(this.getClass()))
        {
            targetDelegate = delegateClassType.cast(this);
        } else
        {
            throw new IllegalStateException("Application's IPlatformDelegate is not assignable to class " +
                    delegateClassType.getName());
        }

        return targetDelegate;

    }
}
